package com.wsjzzcbq.qianyuan.sys.service.impl;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wsjzzcbq.qianyuan.common.bean.PageBean;
import com.wsjzzcbq.qianyuan.common.bean.R;
import com.wsjzzcbq.qianyuan.common.constant.JobStateEnum;
import com.wsjzzcbq.qianyuan.common.constant.MsgConsts;
import com.wsjzzcbq.qianyuan.sys.bean.QuartzJob;
import com.wsjzzcbq.qianyuan.sys.mapper.QuartzJobMapper;
import com.wsjzzcbq.qianyuan.sys.param.AddQuartzJobParam;
import com.wsjzzcbq.qianyuan.sys.param.FindQuartzJobParam;
import com.wsjzzcbq.qianyuan.sys.param.TriggerJobParam;
import com.wsjzzcbq.qianyuan.sys.service.QuartzJobService;
import com.wsjzzcbq.qianyuan.sys.vo.QuartzJobVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.quartz.*;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * QuartzJobServiceImpl
 *
 * @author wsjz
 * @date 2022/03/22
 */
@Slf4j
@Service
public class QuartzJobServiceImpl extends ServiceImpl<QuartzJobMapper, QuartzJob> implements QuartzJobService {

    @Autowired
    private Scheduler scheduler;

    @Autowired
    private QuartzJobMapper quartzJobMapper;

    @Override
    public R<PageBean<QuartzJobVo>> findList(FindQuartzJobParam param) {
        IPage<QuartzJobVo> iPage = quartzJobMapper.findPageList(new Page<>(param.getPageNum(), param.getPageSize()), param);
        iPage.getRecords().forEach(v->v.setStateName(JobStateEnum.getNameByValue(v.getState())));
        PageBean<QuartzJobVo> pageBean = PageBean.create(iPage);
        return R.success(pageBean);
    }

    @Override
    public R<?> addOrUpdate(AddQuartzJobParam param) {
        if (!isValidExpression(param.getCronExpression())) {
            return R.error(MsgConsts.CRON_ERROR);
        }
        if (!isExistClass(param.getJobClass())) {
            return R.error(MsgConsts.JOB_CLASS_NOT_FOUND);
        }
        QuartzJob job = new QuartzJob();

        BeanUtils.copyProperties(param, job);
        if (StringUtils.isBlank(param.getId())) {
            //添加
            try {
                scheduleQuartzJob(job);
                scheduler.pauseJob(JobKey.jobKey(job.getJobClass()));
                job.setState(JobStateEnum.PAUSED.getValue());
                this.save(job);
                return R.success(MsgConsts.ADD_SUCCESS);
            } catch (ClassCastException e) {
                log.error("", e);
                return R.error(MsgConsts.JOB_CLASS_IS_NOT_QUARTZJOB_IMPLEMENT_CLASS);
            } catch (Exception e) {
                log.error("", e);
                return R.error(MsgConsts.ADD_ERROR);
            }
        } else {
            //修改
            QuartzJob quartzJob = this.getById(param.getId());
            if (quartzJob == null) {
                return R.error(MsgConsts.ID_ERROR);
            }
            if (JobStateEnum.RUNNING.getValue().equals(quartzJob.getState())) {
                return R.error(MsgConsts.PLEASE_STOP_TASK_FIRST_UPDATE);
            }

            try {
                schedulerDeleteJob(quartzJob.getJobClass());
                scheduleQuartzJob(job);
                scheduler.pauseJob(JobKey.jobKey(job.getJobClass()));
                job.setId(Long.valueOf(param.getId()));
                this.updateById(job);
                return R.success(MsgConsts.UPDATE_SUCCESS);
            } catch (ClassCastException e) {
                rollbackQuartzJob(quartzJob);
                log.error("", e);
                return R.error(MsgConsts.JOB_CLASS_IS_NOT_QUARTZJOB_IMPLEMENT_CLASS);
            } catch (Exception e) {
                rollbackQuartzJob(quartzJob);
                log.error("", e);
                return R.error(MsgConsts.UPDATE_ERROR);
            }

        }

    }

    /**
     * 效验cron表达式是否正确
     * @param cronExpression
     * @return
     */
    private boolean isValidExpression(String cronExpression) {
        return CronExpression.isValidExpression(cronExpression);
    }

    /**
     * 执行类是否存在
     * @param jobClass
     * @return
     */
    private boolean isExistClass(String jobClass) {
        Object obj = null;
        try {
            Class cls = Class.forName(jobClass);
            obj = cls.newInstance();
            return true;
        } catch (Exception e) {
            return false;
        } finally {
            obj = null;
        }
    }

    /**
     * 回滚 quartz job
     * @param quartzJob
     */
    private void rollbackQuartzJob(QuartzJob quartzJob) {
        try {
            scheduleQuartzJob(quartzJob);
            scheduler.pauseJob(JobKey.jobKey(quartzJob.getJobClass()));
        } catch (Exception e) {
            log.error("rollback quartz job expection: ", e);
        }
    }



    @Override
    public R<?> deleteJob(String id) {
        QuartzJob job = this.getById(Long.valueOf(id));
        if (job == null) {
            return R.error(MsgConsts.ID_ERROR);
        }
        //job处于运行中，不允许删除
        if (JobStateEnum.RUNNING.getValue().equals(job.getState())) {
            return R.error(MsgConsts.PLEASE_STOP_TASK_FIRST_DELETE);
        }
        try {
            schedulerDeleteJob(job.getJobClass());
            this.removeById(job.getId());
            return R.success(MsgConsts.DELETE_SUCCESS);
        } catch (Exception e) {
            return R.success(MsgConsts.DELETE_ERROR);
        }
    }

    private void schedulerDeleteJob(String jobClass) throws SchedulerException {
        TriggerKey triggerKey = TriggerKey.triggerKey(jobClass);
        scheduler.pauseTrigger(triggerKey);
        scheduler.unscheduleJob(triggerKey);
        scheduler.deleteJob(JobKey.jobKey(jobClass));
    }

    @Override
    public R<?> triggerJob(TriggerJobParam param) {
        QuartzJob job = this.getById(Long.valueOf(param.getId()));
        if (job == null) {
            return R.error(MsgConsts.ID_ERROR);
        }
        try {
            JobKey jobKey = new JobKey(job.getJobClass());
            scheduler.triggerJob(jobKey);
            return R.success(MsgConsts.EXECUTE_SUCCESS);
        } catch (Exception e) {
            log.error("", e);
            return R.error(MsgConsts.EXECUTE_ERROR);
        }
    }

    @Override
    public R<?> resumeJob(TriggerJobParam param) {
        QuartzJob job = this.getById(Long.valueOf(param.getId()));
        if (job == null) {
            return R.error(MsgConsts.ID_ERROR);
        }
        if (job.getState().equals(JobStateEnum.RUNNING.getValue())) {
            return R.error(MsgConsts.CURRENT_TASK_IS_RUNNING);
        }
        JobKey jobKey = new JobKey(job.getJobClass());
        try {
            scheduler.resumeJob(jobKey);
            job.setState(JobStateEnum.RUNNING.getValue());
            this.updateById(job);
            return R.success(MsgConsts.RESUME_SUCCESS);
        } catch (Exception e) {
            log.error("", e);
            return R.error(MsgConsts.RESUME_ERROR);
        }
    }

    @Override
    public R<?> pauseJob(TriggerJobParam param) {
        QuartzJob job = this.getById(Long.valueOf(param.getId()));
        if (job == null) {
            return R.error(MsgConsts.ID_ERROR);
        }
        if (job.getState().equals(JobStateEnum.PAUSED.getValue())) {
            return R.error(MsgConsts.CURRENT_TASK_PAUSED);
        }
        JobKey jobKey = new JobKey(job.getJobClass());
        try {
            scheduler.pauseJob(jobKey);
            job.setState(JobStateEnum.PAUSED.getValue());
            this.updateById(job);
            return R.success(MsgConsts.PAUSE_SUCCESS);
        } catch (Exception e) {
            log.error("", e);
            return R.error(MsgConsts.PAUSE_ERROR);
        }
    }

    @Override
    public void scheduleQuartzJob(QuartzJob job) throws ClassNotFoundException, SchedulerException, IllegalAccessException, InstantiationException {
        Class cls = Class.forName(job.getJobClass());
        Job obj = (Job)cls.newInstance();
        JobDetail jobDetail = JobBuilder.newJob(obj.getClass()).withIdentity(job.getJobClass()).build();

        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());

        CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(job.getJobClass()).withSchedule(cronScheduleBuilder).build();

        if (!scheduler.checkExists(jobDetail.getKey())) {
            scheduler.scheduleJob(jobDetail, cronTrigger);
        }

    }
}
